//
//  BLEDevice.m
//  BLESDKDemo
//
//  Created by 李阳 on 2018/4/5.
//  Copyright © 2018年 李阳. All rights reserved.
//

#import "BLEDevice.h"

#import "BLEOperation.h"
#import "BLECenter.h"
#import "NSError+BLEAdd.h"
#import "NSArray+BLEAdd.h"
#import "BLERequest.h"
#import "BLESession.h"

/// 蓝牙数据传输
@interface BLERequest ()

#pragma mark Response

/// 响应数据的长度
@property (nonatomic, assign) NSInteger responseDataLength;
/// 实际响应的次数
@property (nonatomic, assign) NSInteger responseTimes;
/// 响应的数据
@property (nonatomic, strong) NSData *responseData;
/// 响应的数据数组 已做好截取
@property (nonatomic, strong) NSArray<NSData *> *responseDatas;

@end

@interface BLEOperation ()

/// 需要更新的通知特征名称
@property (nonatomic, copy) NSArray<NSString *>* needUpdateNotifyValueCharacteristicNames;

@end


@interface BLEDevice ()
<CBPeripheralDelegate,
BLESessionDelegate,
BLESessionTaskDelegate>

/// 蓝牙中心
@property (nonatomic, weak) BLECenter *center;
/// 设备名称
@property (nonatomic, copy) NSString *name;
/// 设备状态
@property (nonatomic, assign)BLEDeviceConnectState state;
/// 信号强度
@property (nonatomic, strong)NSNumber *RSSI;
/// 外设
@property (nonatomic, strong) CBPeripheral *peripheral;
///
@property (nonatomic, assign) BOOL retrieveFromCenter;

/// 读取信号强度的操作
@property (nonatomic, strong) BLEOperation *readRSSIOp;

/// 准备交互的操作 发现服务，特征，设置通知状态
@property (nonatomic, strong) BLEOperation *prepareOp;

/// 设备功能状态
@property (nonatomic, assign) BLEDeviceReachabilityStatus status;

@property (nonatomic, strong) CBCharacteristic *mainNotifyC;
@property (nonatomic, strong) CBCharacteristic *mainWriteC;

@property (nonatomic, strong) CBCharacteristic *nfcNotifyC;
@property (nonatomic, strong) CBCharacteristic *nfcWriteC;

@property (nonatomic, strong) CBCharacteristic *mediaNotifyC;
@property (nonatomic, strong) CBCharacteristic *mediaWriteC;

@property (nonatomic, strong) CBCharacteristic *heartNotifyC;
//@property (nonatomic, strong) CBCharacteristic *heartWriteC;

@property (nonatomic, strong) CBCharacteristic *payNotifyC;
@property (nonatomic, strong) CBCharacteristic *payWriteC;

@property (nonatomic, strong) CBCharacteristic *otaNotifyC;
@property (nonatomic, strong) CBCharacteristic *otaWriteC;

@property (nonatomic, strong) CBCharacteristic *otaL42NotifyC;
@property (nonatomic, strong) CBCharacteristic *otaL42WriteC;

@end


@implementation BLEDevice
{
    dispatch_semaphore_t _lock;
    __unsafe_unretained BLENormalTask *_currentNormalTask;
    __unsafe_unretained BLEMediaTask *_currentMedialTask;
    __unsafe_unretained BLEContactTask *_currentContactTask;
}

#pragma mark Request

- (void)subscribeWithCmd:(Byte)cmd
       advertisedChannel:(BLEAdvertisedChannel)channel
                    next:(void(^)(NSData *responseData, Byte cmd))block
{
    
}

- (void)beginNormalTask:(BLENormalTask *)task
{
    _currentNormalTask = task;
    
    BLERequest *request = task.request;
    
    switch (request.writeChannel) {
        case BLEMainWritableChannel:
            
            break;
            
        default:
            break;
    }
    
}
- (void)beginMediaTask:(BLEMediaTask *)task
{
    
}
- (void)beginContactTask:(BLEContactTask *)task
{
    
}



#pragma mark Write Data
- (void) writeData:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic
{
    
}

#pragma mark ReadRSSI
- (void)readRSSIWithTimeout:(NSTimeInterval)timeout completion:(void (^)(NSTimeInterval, NSNumber *, NSError *))completion
{
    if (_readRSSIOp)
    {
        !completion ?: completion(kNoneInterval, nil, [NSError bleBusyError]);
        return;
    }
    
    BLEOperation *op = [[BLEOperation alloc]init];
    op.type = BLEOperationTypeReadRSSI;
    if (timeout)
    {
        op.timeout = timeout;
    }
    __weak __typeof__ (self) weakSelf = self;
    [op setFireBlock:^(BLEOperation *op, NSTimeInterval use) {
        __strong __typeof__ (weakSelf) self = weakSelf;
        [self.peripheral readRSSI];
    }];
    [op setInvalidateBlock:^(BLEOperation *op, NSTimeInterval use, NSError *error) {
        __strong __typeof__ (weakSelf) self = weakSelf;
        self.readRSSIOp = nil;
        NSNumber *RSSI = error ? nil : self.RSSI;
        !completion ?: completion(use, RSSI, error);
    }];
    
    self.readRSSIOp = op;
    [op fire];
}

#pragma mark Prepare

- (void)prepareForSessionWithTimeout:(NSTimeInterval)timeout
                          completion:(void (^)(NSTimeInterval, NSError *))completion
{
    if (self.status & BLEDeviceReachabilityStatusSession)
    {
        !completion ?: completion(kNoneInterval, nil);
        return;
    }
    BLECenterConfig *con = self.center.config;
    BLEOperation *op = [self _prepareForGroups:@[con.main,
                              con.NFC,
                              con.media,
                              con.heart,
                              con.pay,
                              con.OTA,
                              con.OTAL42]
                    timeout:timeout
                 completion:completion];
    op.needUpdateNotifyValueCharacteristicNames = @[con.main.notifyCharacteristicName];
}

- (void)prepareForNFCWithTimeout:(NSTimeInterval)timeout
                      completion:(void(^)(NSTimeInterval use, NSError *error))completion
{
    if (self.status & BLEDeviceReachabilityStatusNFC)
    {
        !completion ?: completion(kNoneInterval, nil);
        return;
    }
    BLECenterConfig *con = self.center.config;
    BLEOperation *op = [self _prepareForGroups:@[con.NFC]
                    timeout:timeout
                 completion:completion];
    op.needUpdateNotifyValueCharacteristicNames = @[con.NFC.notifyCharacteristicName];
}

- (void)prepareForPayWithTimeout:(NSTimeInterval)timeout
                      completion:(void(^)(NSTimeInterval use, NSError *error))completion
{
    if (self.status & BLEDeviceReachabilityStatusPay)
    {
        !completion ?: completion(kNoneInterval, nil);
        return;
    }
    BLECenterConfig *con = self.center.config;
    BLEOperation *op = [self _prepareForGroups:@[con.pay]
                    timeout:timeout
                 completion:completion];
    op.needUpdateNotifyValueCharacteristicNames = @[con.pay.notifyCharacteristicName];
}

- (void)prepareForOTAWithTimeout:(NSTimeInterval)timeout
                      completion:(void(^)(NSTimeInterval use, NSError *error))completion
{
    if (self.status & BLEDeviceReachabilityStatusOTA)
    {
        !completion ?: completion(kNoneInterval, nil);
        return;
    }
    BLECenterConfig *con = self.center.config;
    NSArray *items = @[con.OTA,
                       con.OTAL42];
    BLEOperation *op = [self _prepareForGroups:items
                    timeout:timeout
                 completion:completion];
    op.needUpdateNotifyValueCharacteristicNames = [items valueForKeyPath:@"notifyCharacteristicName"];
}

- (BLEOperation *)_prepareForGroups:(NSArray<BLEUUIDItem *>*)items
                 timeout:(NSTimeInterval)timeout
              completion:(void (^)(NSTimeInterval use, NSError *error))completion
{
    if (_prepareOp)
    {
        !completion ?: completion(kNoneInterval, [NSError bleBusyError]);
        return _prepareOp;
    }
    
    NSArray *serverUUIDs = [[items valueForKeyPath:@"serviceName"] ble_map:^id(NSString *obj, NSUInteger idx) {
        return [CBUUID UUIDWithString:obj];
    }];
    
    BLEOperation *op = [[BLEOperation alloc]init];
    op.type = BLEOperationTypePrepareSession;
    
    if (timeout)
    {
        op.timeout = timeout;
    }
    __weak __typeof__ (self) weakSelf = self;
    [op setFireBlock:^(BLEOperation *op, NSTimeInterval use) {
        __strong __typeof__ (weakSelf) self = weakSelf;
        self.peripheral.delegate = self;
        [self.peripheral discoverServices:serverUUIDs];
    }];
    [op setInvalidateBlock:^(BLEOperation *op, NSTimeInterval use, NSError *error) {
        __strong __typeof__ (weakSelf) self = weakSelf;
        !completion ?: completion( use, error);
        self.prepareOp = nil;
    }];
    
    self.prepareOp = op;
    [op fire];
    return op;
}


#pragma mark - Private

/// 停止监听state
- (void)endOberverState
{
    self.peripheral.delegate = nil;
    [self.peripheral ble_removeObserverBlocksForKeyPath:@"state"];
}
/// 开始监听state
- (void)beginOberverState
{
    [self.peripheral ble_addObserverBlockForKeyPath:@"state" block:^(id  _Nonnull obj, id  _Nullable oldVal, id  _Nullable newVal) {
        CBPeripheralState state = [newVal integerValue];
        self.state = (BLEDeviceConnectState)state;
        if (state != CBPeripheralStateConnected)
        {
            self.status = BLEDeviceReachabilityStatusNotReachable;
        }
    }];
}



#pragma mark - CBPeripheralDelegate

- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error
{
    if (!error)
    {
        self.RSSI = RSSI;
    }
    [self.readRSSIOp invalidateWithError:error];
}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    if (error)
    {
        [self.prepareOp invalidateWithError:error];
    }
    else
    {
        BLECenterConfig *con = self.center.config;
        for (CBService *service in peripheral.services)
        {
            NSString *UUIDString = service.UUID.UUIDString;
            BLELog(@"发现服务%@", UUIDString);
            if ([UUIDString isEqualToString:con.main.serviceName])
            {
                NSMutableArray *UUIDs = con.main.characteristicUUIDs;
                [UUIDs addObjectsFromArray:con.media.characteristicUUIDs];
                [peripheral discoverCharacteristics:UUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.NFC.serviceName])
            {
                [peripheral discoverCharacteristics:con.NFC.characteristicUUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.media.serviceName])
            {
                [peripheral discoverCharacteristics:con.media.characteristicUUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.heart.serviceName])
            {
                [peripheral discoverCharacteristics:con.heart.characteristicUUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.pay.serviceName])
            {
                [peripheral discoverCharacteristics:con.pay.characteristicUUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.OTA.serviceName])
            {
                [peripheral discoverCharacteristics:con.OTA.characteristicUUIDs forService:service];
            }
            if ([UUIDString isEqualToString:con.OTAL42.serviceName])
            {
                [peripheral discoverCharacteristics:con.OTAL42.characteristicUUIDs forService:service];
            }
        }
    }
}


- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    if (error)
    {
        [self.prepareOp invalidateWithError:error];
    }
    else
    {
        BLECenterConfig *con = self.center.config;
        
        NSString *UUIDString = service.UUID.UUIDString;
        
        for (CBCharacteristic *c in service.characteristics)
        {
            BLELog(@"发现特征%@，隶属于服务%@", c.UUID.UUIDString, UUIDString);
        }
        
        if ([UUIDString isEqualToString:con.main.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.main.writeCharacteristicName])
                {
                    self.mainWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.main.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.mainNotifyC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.media.writeCharacteristicName])
                {
                    self.mediaWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.media.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.mediaNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.NFC.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.NFC.writeCharacteristicName])
                {
                    self.nfcWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.NFC.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.nfcNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.media.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.media.writeCharacteristicName])
                {
                    self.mediaWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.media.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.mediaNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.heart.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.heart.writeCharacteristicName])
                {
//                    self.heartWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.NFC.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.heartNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.pay.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.pay.writeCharacteristicName])
                {
                    self.payWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.pay.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.payNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.OTA.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.OTA.writeCharacteristicName])
                {
                    self.otaWriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.OTA.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.otaNotifyC = characteristic;
                }
            }
        }
        if ([UUIDString isEqualToString:con.OTAL42.serviceName])
        {
            for (CBCharacteristic *characteristic in service.characteristics)
            {
                NSString *charUUIDString = characteristic.UUID.UUIDString;
                if ([charUUIDString isEqualToString:con.OTAL42.writeCharacteristicName])
                {
                    self.otaL42WriteC = characteristic;
                }
                if ([charUUIDString isEqualToString:con.OTAL42.notifyCharacteristicName])
                {
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    self.otaL42NotifyC = characteristic;
                }
            }
        }
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSString *UUIDString = characteristic.UUID.UUIDString;
    BLELog(@"特征%@可以接收通知", UUIDString);
    
    if (!error)
    {
        if ([UUIDString isEqualToString:self.mainNotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusSession;
        }
        if ([UUIDString isEqualToString:self.mediaNotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusMedia;
        }
        if ([UUIDString isEqualToString:self.heartNotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusHeart;
        }
        if ([UUIDString isEqualToString:self.nfcNotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusNFC;
        }
        if ([UUIDString isEqualToString:self.payNotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusPay;
        }
        if ([UUIDString isEqualToString:self.otaNotifyC.UUID.UUIDString] ||
            [UUIDString isEqualToString:self.otaL42NotifyC.UUID.UUIDString])
        {
            self.status |= BLEDeviceReachabilityStatusOTA;
        }
    }
    if ([self.prepareOp.needUpdateNotifyValueCharacteristicNames containsObject:UUIDString])
    {
        [self.prepareOp invalidateWithError:error];
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    
}








#pragma mark Thread

+ (void)commandRequestThreadEnteryPoint
{
    @autoreleasepool
    {
        [[NSThread currentThread] setName:@"BLESDK"];
        
        //添加端口，防止runloop直接退出
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)commandRequestThread
{
    static NSThread *_commandRequestThread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _commandRequestThread = [[NSThread alloc]initWithTarget:self selector:@selector(commandRequestThread) object:nil];
        [_commandRequestThread start];
    });
    return _commandRequestThread;
}

#pragma mark NSObject

+ (instancetype)deviceWithPeripheral:(CBPeripheral *)peripheral
                                RSSI:(NSNumber *)rssi
                   advertisementData:(NSDictionary<NSString *,id> *)advertisementData
{
    return [[self alloc] initWithPeripheral:peripheral
                                       RSSI:rssi
                          advertisementData:advertisementData];
}

- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral
                              RSSI:(NSNumber *)rssi
                 advertisementData:(NSDictionary<NSString *,id> *)advertisementData
{
    NSString *name = advertisementData[@"kCBAdvDataLocalName"];
    if (![name isKindOfClass:[NSString class]] || !name.length)
    {
        name = peripheral.name;
    }
    if (!name.length) return nil;
    self = [super init];
    if (!self) return nil;
    _peripheral = peripheral;
    _RSSI = rssi;
    _name = name;
    _lock = dispatch_semaphore_create(1);
    return self;
}

- (NSComparisonResult)compareDevice:(BLEDevice *)device
{
    return [device.RSSI compare:self.RSSI];
}

- (BOOL)isEqualToDevice:(BLEDevice *)device
{
    return [self.peripheral.name isEqualToString:device.peripheral.name];
}

- (BOOL)isEqual:(id)object
{
    if (self == object) return YES;
    if (![object isKindOfClass:[self class]]) return NO;
    return [self isEqualToDevice:object];
}

- (NSUInteger)hash
{
    return self.peripheral.name.hash;
}


#pragma mark Units


- (void)lock
{
    dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
}
- (void)unlock
{
    dispatch_semaphore_signal(_lock);
}


#pragma mark Getter Setter



@end
